#include "stdafx.h"

#include "video/program.h"
#include "core/logger.h"

namespace natrium {
	namespace video {
		Program::Program():
			mHandle(0),
			mLinked(false)
		{
		}

		Program::~Program() {
			if (mHandle != 0)
				glDeleteProgram(mHandle);

			for (auto& handle: mAttachedShaders)
				glDeleteShader(handle);
		}

		bool Program::compile(const std::string& source, eShaderType type) {
			if (mHandle <= 0) {
				mHandle = glCreateProgram();

				if (mHandle == 0) {
					gLog.error() << "video::Program::compile - Failed to create program";
					return false;
				}
			}

			GLuint shader = 0;

			//disallow unsupported shader types
			assert(
				(type == VERTEX) ||
				(type == FRAGMENT) ||
				(type == GEOMETRY) ||
				(type == TESS_CONTROL) ||
				(type == TESS_EVAL) ||
				(type == COMPUTE)
			);

			shader = glCreateShader(type);

			const char* goddamnAncientStringType = source.c_str();
			glShaderSource(shader, 1, &goddamnAncientStringType, NULL);
			glCompileShader(shader);

			//check and report any errors
			int result;
		
			glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
		
			if (result == GL_FALSE) {
				int logLength = 0;

				//fetch the log message size
				glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength);

				//aand the actual message
				if (logLength > 0) {
					char* message = new char[logLength];
					int written = 0;
				
					glGetShaderInfoLog(shader, logLength, &written, message);
					gLog.error() << "Shader compilation failed: " << message; //forward it to the global logger

					delete[] message;
				}

				return false;
			}

			//everything went fine, attach the shader to this program and return
			glAttachShader(mHandle, shader);
			mAttachedShaders.push_back(shader);
		
			return true;
		}

		bool Program::compileFile(const std::string& filename, eShaderType type) {
			std::ifstream ifs(filename);

			if (!ifs) {
				gLog.error() << "video::Program::compile - Failed to open file: " << filename;
				return false;
			}

			std::string line;
			std::string source;

			while (std::getline(ifs, line)) {
				source += line;
				source += '\n';
			}

			ifs.close();

			gLog.info() << "video::Program::compile - compiling file: " << filename;
			return compile(source, type);
		}

		bool Program::link() {
			if (mLinked)
				return true;

			if (mHandle == 0)
				return false;

			glLinkProgram(mHandle);

			//check for any errors
			int status = 0;
			glGetProgramiv(mHandle, GL_LINK_STATUS, &status);

			if (status == GL_FALSE) {
				int logLength = 0;

				//fetch the log message size
				glGetProgramiv(mHandle, GL_INFO_LOG_LENGTH, &logLength);

				//aand the actual message
				if (logLength > 0) {
					char* message = new char[logLength];
					int written = 0;
				
					glGetProgramInfoLog(mHandle, logLength, &written, message);
					gLog.error() << "Program linking failed: " << message; //forward it to the global logger

					delete[] message;
				}

				return false;
			}

			mLinked = true;
		
			return true;
		}

		void Program::use() {
			if (mHandle == 0)
				return;

			if (!mLinked)
				return;

			glUseProgram(mHandle);
		}

		bool Program::isLinked() const {
			return mLinked;
		}

		void Program::bindAttribLocation(GLuint location, const std::string& name) {
			glBindAttribLocation(mHandle, location, name.c_str());
		}

		void Program::bindFragDataLocation(GLuint location, const std::string& name) {
			glBindFragDataLocation(mHandle, location, name.c_str());
		}

		void Program::set(const std::string& name, float x, float y, float z) {
			GLint location = getUniformLocation(name);
		
			if (location >= 0)
				glUniform3f(location, x, y, z);
		}

		void Program::set(const std::string& name, float x, float y, float z, float w) {
			GLint location = getUniformLocation(name);
		
			if (location >= 0)
				glUniform4f(location, x, y, z, w);
		}

		void Program::set(const std::string& name, const glm::vec3& v) {
			set(name, v.x, v.y, v.z);
		}

		void Program::set(const std::string& name, const glm::vec4& v) {
			set(name, v.x, v.y, v.z, v.w);
		}

		void Program::set(const std::string& name, const glm::mat3& m) {
			GLint location = getUniformLocation(name);

			if (location >= 0)
				glUniformMatrix3fv(location, 1, GL_FALSE, &m[0][0]);
		}

		void Program::set(const std::string& name, const glm::mat4& m) {
			GLint location = getUniformLocation(name);

			if (location >= 0)
				glUniformMatrix4fv(location, 1, GL_FALSE, &m[0][0]);
		}

		void Program::set(const std::string& name, float value) {
			GLint location = getUniformLocation(name);

			if (location >= 0)
				glUniform1f(location, value);
		}

		void Program::set(const std::string& name, int value) {
			GLint location = getUniformLocation(name);

			if (location >= 0)
				glUniform1i(location, value);
		}

		void Program::set(const std::string& name, bool value) {
			GLint location = getUniformLocation(name);

			if (location >= 0)
				glUniform1i(location, value); //there is no special function for booleans, but conveniently integers will do
		}

		GLint Program::getUniformLocation(const std::string& name) {
			return glGetUniformLocation(mHandle, name.c_str()); //silent fail...
		}

		void Program::listActiveUniforms() const {
			GLint numUniforms;
			GLint size;
			GLint location;
			GLsizei maxLen;
			GLchar* name;
			GLsizei written;
			GLenum type;

			glGetProgramiv(mHandle, GL_ACTIVE_UNIFORM_MAX_LENGTH, &maxLen);
			glGetProgramiv(mHandle, GL_ACTIVE_UNIFORMS, &numUniforms);

			name = new GLchar[maxLen];

			gLog << "Location\t->\tName";
			gLog << "--------------------------------------------------------------";
		
			for (GLint i = 0; i < numUniforms; ++i) {
				glGetActiveUniform(mHandle, i, maxLen, &written, &size, &type, name);
				location = glGetUniformLocation(mHandle, name);
				gLog << location << "\t->\t" << name;
			}

			gLog << "--------------------------------------------------------------";

			delete[] name;
		}

		void Program::listActiveAttribs() const {
			GLint numAttribs;
			GLint size;
			GLint index;
			GLsizei maxLen;
			GLchar* name;
			GLsizei written;
			GLenum type;

			glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, &maxLen);
			glGetProgramiv(mHandle, GL_ACTIVE_ATTRIBUTES, &numAttribs);

			name=  new GLchar[maxLen];

			gLog << "Index\t->\tName";
			gLog << "--------------------------------------------------------------";

			for (GLint i = 0; i < numAttribs; ++i) {
				glGetActiveAttrib(mHandle, i, maxLen, &written, &size, &type, name);
				index = glGetAttribLocation(mHandle, name);
				gLog << index << "\t->\t" << name;
			}

			gLog << "--------------------------------------------------------------";

			delete[] name;
		}

		GLuint Program::getHandle() const {
			return mHandle;
		}
	}
}